#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <iostream>
#include <chrono>
#include "common.h"

/*
 * Get physical address of any mapped virtual address in the current process.
 */

unsigned long mem_virt2phy(const void *virtaddr)
{
	int fd, retval;
	uint64_t page, physaddr;
	unsigned long virt_pfn;
	int page_size;
	off_t offset;

	/* standard page size */
	page_size = getpagesize();

	fd = open("/proc/self/pagemap", O_RDONLY);
	if (fd < 0) {
		std::cout<< "cannot open /proc/self/pagemap" << std::endl;
		return 0;
	}

	virt_pfn = (unsigned long)virtaddr / page_size;
	offset = sizeof(uint64_t) * virt_pfn;
    off_t offset_res = lseek(fd, offset, SEEK_SET);
	if (offset_res == (off_t) -1) {
		std::cout<< "seek error in /proc/self/pagemap"<< std::endl;
		close(fd);
	}

    int expect_size = sizeof(long);
	retval = read(fd, &page, expect_size);
	close(fd);
	if (retval < 0) {
		std::cout<< "cannot read /proc/self/pagemap" << std::endl;
		return 0;
	} else if (retval != expect_size) {
		std::cout<< "read " << retval << " bytes from /proc/self/pagemap, "
				"but expected " << expect_size << std::endl;
        std::cout<< "expect offset = " << offset << ", result offset = "
                << offset_res << std::endl;
		return 0;
	}

	/*
	 * the pfn (page frame number) are bits 0-54 (see
	 * pagemap.txt in linux Documentation)
	 */
	if ((page & 0x7fffffffffffffULL) == 0)
    {
        std::cout<< "abnormal page = " << page << std::endl;
        return 0;
    }
		

	physaddr = ((page & 0x7fffffffffffffULL) * page_size)
		+ ((unsigned long)virtaddr % page_size);

	return physaddr;
}

int main(int argc, char* argv[])
{
    int fd = open("/dev/hello", O_RDWR, S_IRUSR|S_IWUSR);
    if(fd != -1)
    {
        struct AddrInfo addrInfo = {0};
        int res = ioctl(fd, 0xC0506D14, (unsigned long)&addrInfo);
        std::cout<< "alloc memory result:\n";
        std::cout<< std::hex << "\tphyAddr: " << addrInfo.phyAddr << "\n";
        std::cout<< "\tvirKernelAddr: " << addrInfo.virKernelAddr << "\n";
        std::cout<< "\tvirUserAddr: " << addrInfo.virUserAddr << "\n\n";
        *(int*)addrInfo.virUserAddr = 123456;

        {
            struct AddrQueryResult queryRes = {0, addrInfo.virKernelAddr};
            ioctl(fd, 0xC0506D15, (unsigned long)&queryRes);
            std::cout<< "query virKernelAddr by kernel:\n";
            std::cout<< "\tphyAddr: " << queryRes.phyAddr << "\n\n";
        }
        {
            struct AddrQueryResult queryRes = {0, addrInfo.virUserAddr};
            ioctl(fd, 0xC0506D15, (unsigned long)&queryRes);
            std::cout<< "query virUserAddr by kernel:\n";
            std::cout<< "\tphyAddr: " << queryRes.phyAddr << "\n\n";
        }
        {
            unsigned long virAddr = addrInfo.virKernelAddr;
            unsigned phyAddr = mem_virt2phy((void*)virAddr);
            std::cout<< "query virKernelAddr by user:\n";
            std::cout<< "\tphyAddr: " << phyAddr << "\n\n";
        }
        {
            unsigned long virAddr = addrInfo.virUserAddr;
            unsigned phyAddr = mem_virt2phy((void*)virAddr);
            std::cout<< "query virUserAddr by user:\n";
            std::cout<< "\tphyAddr: " << phyAddr << "\n\n";
        }
        
        {
            int* p = new int;
            *p = 123456;
            struct AddrQueryResult queryRes = {0, (unsigned long)p};
            ioctl(fd, 0xC0506D15, (unsigned long)&queryRes);
            std::cout<< "query malloc addr by kernel:\n";
            std::cout<< "\tphyAddr: " << queryRes.phyAddr << "\n\n";

            unsigned long phyAddr = mem_virt2phy(p);
            std::cout<< "query malloc addr by user:\n";
            std::cout<< "\tphyAddr: " << phyAddr << "\n\n";

            delete p;
        }
    }
    else
    {
        perror("Failed at open():");
    }
    return 0;
}

